////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//            This file is distributed under the BSD 2-Clause open source license. See License.txt for details.               //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#pragma once

#ifndef __WN_MATH_INTERNAL_X86_SSE_ELEMENTS_INL__
#define __WN_MATH_INTERNAL_X86_SSE_ELEMENTS_INL__

#ifndef __WN_MATH_INTERNAL_ELEMENTS_H__
    #error "Internal/x86/SSE/WNElements.inl should never be included directly."
#elif !defined _WN_X86
    #error "Internal/x86/SSE/WNElements.inl has been included on a non x86 platform. Please rectify this."
#elif !defined __WN_MATH_ENABLE_EXTENSIONS
    #error "Internal/x86/SSE/WNElements.inl has been included when extensions are disabled. Please rectify this."
#elif !defined __WN_MATH_SSE_ALLOWED
    #error "Internal/x86/SSE/WNElements.inl has been included when SSE is not allowed. Please rectify this."
#endif

namespace WNMath {
    namespace __WNInternal {
        #ifdef __WN_MATH_SSE_AVAILABLE
            // N Elements

            template <WN_UINT32 Dimension>
            __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, Dimension>& __WNElementsSetSSE(__WNElements<WN_FLOAT32, Dimension>& _element, const WN_FLOAT32& _number) {
                const WN_UINT32 ne = Dimension & 3;
                const WN_UINT32 nf = (Dimension - ne) >> 2;
                const __m128 n = _mm_set1_ps(_number);

                for (WN_UINT32 i = 0; i < nf; ++i) {
                    _element.mXMMValues[i] = n;
                }

                switch (ne) {
                    case 3: _element.mXMMValues[nf] = _mm_setr_ps(_number, _number, _number, 0.0f); break;
                    case 2: _element.mXMMValues[nf] = _mm_setr_ps(_number, _number, 0.0f, 0.0f); break;
                    case 1: _element.mXMMValues[nf] = _mm_setr_ps(_number, 0.0f, 0.0f, 0.0f); break;
                    default: break;
                }

                return(_element);
            }

            template <WN_UINT32 Dimension>
            __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, Dimension>& __WNElementsSetSSE(__WNElements<WN_FLOAT32, Dimension>& _element, const WN_FLOAT32* _numbers) {
                const WN_UINT32 ne = Dimension & 3;
                const WN_UINT32 nf = (Dimension - ne) >> 2;

                for (WN_UINT32 i = 0; i < nf; ++i) {
                    _element.mXMMValues[i] = _mm_setr_ps(_numbers[(i * 4)], _numbers[(i * 4) + 1], _numbers[(i * 4) + 2], _numbers[(i * 4) + 3]);
                }

                switch (ne) {
                    case 3: _element.mXMMValues[nf] = _mm_setr_ps(_numbers[Dimension - 3], _numbers[Dimension - 2], _numbers[Dimension - 1], 0.0f); break;
                    case 2: _element.mXMMValues[nf] = _mm_setr_ps(_numbers[Dimension - 2], _numbers[Dimension - 1], 0.0f, 0.0f); break;
                    case 1: _element.mXMMValues[nf] = _mm_setr_ps(_numbers[Dimension - 1], 0.0f, 0.0f, 0.0f); break;
                    default: break;
                }

                return(_element);
            }

            // 4 Elements

            template <>
            __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, 4>& __WNElementsSetSSE(__WNElements<WN_FLOAT32, 4>& _element, const WN_FLOAT32& _number) {
                _element.mXMMValues[0] = _mm_set1_ps(_number);

                return(_element);
            }

            // 3 Elements

            template <>
            __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, 3>& __WNElementsSetSSE(__WNElements<WN_FLOAT32, 3>& _element, const WN_FLOAT32& _number) {
                _element.mXMMValues[0] = _mm_setr_ps(_number, _number, _number, 0.0f);

                return(_element);
            }

            // 2 Elements

            template <>
            __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, 2>& __WNElementsSetSSE(__WNElements<WN_FLOAT32, 2>& _element, const WN_FLOAT32& _number) {
                _element.mXMMValues[0] = _mm_setr_ps(_number, _number, 0.0f, 0.0f);

                return(_element);
            }
        #endif

        // N Elements

        template <WN_UINT32 Dimension>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, Dimension>& __WNElementsAddSSE(__WNElements<WN_FLOAT32, Dimension>& _element1, const __WNElements<WN_FLOAT32, Dimension>& _element2) {
            #ifdef __WN_MATH_SSE_AVAILABLE
                const WN_UINT32 nr = (Dimension + 3) >> 2;
            #else
                const WN_UINT32 ne = Dimension & 3;
                const WN_UINT32 nr = (Dimension - ne) >> 2;
            #endif


            for (WN_UINT32 i = 0; i < nr; ++i) {
                #ifdef __WN_MATH_SSE_AVAILABLE
                    _element1.mXMMValues[i] = _mm_add_ps(_element1.mXMMValues[i], _element2.mXMMValues[i]);
                #else
                    WN_FLOAT32* a1 = &(_element1.mValues[i << 2]);
                    const WN_FLOAT32* a2 = &(_element2.mValues[i << 2]);

                    _mm_store_ps(a1, _mm_add_ps(_mm_load_ps(a1), _mm_load_ps(a2)));
                #endif
            }

            #ifndef __WN_MATH_SSE_AVAILABLE
                for (WN_UINT32 i = ne; i > 0; --i) {
                    _element1.mValues[Dimension - i] += _element2.mValues[Dimension - i];
                }
            #endif

            return(_element1);
        }

        template <WN_UINT32 Dimension>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, Dimension>& __WNElementsAddSSE(__WNElements<WN_FLOAT32, Dimension>& _element, const WN_FLOAT32& _number) {
            #ifdef __WN_MATH_SSE_AVAILABLE
                const WN_UINT32 nr = (Dimension + 3) >> 2;
            #else
                const WN_UINT32 ne = Dimension & 3;
                const WN_UINT32 nr = (Dimension - ne) >> 2;
            #endif

            const __m128 n = _mm_set1_ps(_number);

            for (WN_UINT32 i = 0; i < nr; ++i) {
                #ifdef __WN_MATH_SSE_AVAILABLE
                    _element.mXMMValues[i] = _mm_add_ps(_element.mXMMValues[i], n);
                #else
                    WN_FLOAT32* a = &(_element.mValues[i << 2]);

                    _mm_store_ps(a, _mm_add_ps(_mm_load_ps(a), n));
                #endif
            }

            #ifndef __WN_MATH_SSE_AVAILABLE
                for (WN_UINT32 i = ne; i > 0; --i) {
                    _element.mValues[Dimension - i] += _number;
                }
            #endif

            return(_element);
        }

        template <WN_UINT32 Dimension>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, Dimension>& __WNElementsSubtractSSE(__WNElements<WN_FLOAT32, Dimension>& _element1, const __WNElements<WN_FLOAT32, Dimension>& _element2) {
            #ifdef __WN_MATH_SSE_AVAILABLE
                const WN_UINT32 nr = (Dimension + 3) >> 2;
            #else
                const WN_UINT32 ne = Dimension & 3;
                const WN_UINT32 nr = (Dimension - ne) >> 2;
            #endif

            for (WN_UINT32 i = 0; i < nr; ++i) {
                #ifdef __WN_MATH_SSE_AVAILABLE
                    _element1.mXMMValues[i] = _mm_sub_ps(_element1.mXMMValues[i], _element2.mXMMValues[i]);
                #else
                    const WN_FLOAT32* a2 = &(_element2.mValues[i << 2]);
                    WN_FLOAT32* a1 = &(_element1.mValues[i << 2]);

                    _mm_store_ps(a1, _mm_sub_ps(_mm_load_ps(a1), _mm_load_ps(a2)));
                #endif
            }

            #ifndef __WN_MATH_SSE_AVAILABLE
                for (WN_UINT32 i = ne; i > 0; --i) {
                    _element1.mValues[Dimension - i] -= _element2.mValues[Dimension - i];
                }
            #endif

            return(_element1);
        }

        template <WN_UINT32 Dimension>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, Dimension>& __WNElementsSubtractSSE(__WNElements<WN_FLOAT32, Dimension>& _element, const WN_FLOAT32& _number) {
            #ifdef __WN_MATH_SSE_AVAILABLE
                const WN_UINT32 nr = (Dimension + 3) >> 2;
            #else
                const WN_UINT32 ne = Dimension & 3;
                const WN_UINT32 nr = (Dimension - ne) >> 2;
            #endif

            const __m128 n = _mm_set1_ps(_number);

            for (WN_UINT32 i = 0; i < nr; ++i) {
                #ifdef __WN_MATH_SSE_AVAILABLE
                    _element.mXMMValues[i] = _mm_sub_ps(_element.mXMMValues[i], n);
                #else
                    WN_FLOAT32* a = &(_element.mValues[i << 2]);

                    _mm_store_ps(a, _mm_sub_ps(_mm_load_ps(a), n));
                #endif
            }

            #ifndef __WN_MATH_SSE_AVAILABLE
                for (WN_UINT32 i = ne; i > 0; --i) {
                    _element.mValues[Dimension - i] -= _number;
                }
            #endif

            return(_element);
        }

        template <WN_UINT32 Dimension>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, Dimension>& __WNElementsMultiplySSE(__WNElements<WN_FLOAT32, Dimension>& _element1, const __WNElements<WN_FLOAT32, Dimension>& _element2) {
            #ifdef __WN_MATH_SSE_AVAILABLE
                const WN_UINT32 nr = (Dimension + 3) >> 2;
            #else
                const WN_UINT32 ne = Dimension & 3;
                const WN_UINT32 nr = (Dimension - ne) >> 2;
            #endif

            for (WN_UINT32 i = 0; i < nr; ++i) {
                #ifdef __WN_MATH_SSE_AVAILABLE
                    _element1.mXMMValues[i] = _mm_mul_ps(_element1.mXMMValues[i], _element2.mXMMValues[i]);
                #else
                    WN_FLOAT32* a1 = &(_element1.mValues[i << 2]);
                    const WN_FLOAT32* a2 = &(_element2.mValues[i << 2]);

                    _mm_store_ps(a1, _mm_mul_ps(_mm_load_ps(a1), _mm_load_ps(a2)));
                #endif
            }

            #ifndef __WN_MATH_SSE_AVAILABLE
                for (WN_UINT32 i = ne; i > 0; --i) {
                    _element1.mValues[Dimension - i] *= _element2.mValues[Dimension - i];
                }
            #endif

            return(_element1);
        }

        template <WN_UINT32 Dimension>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, Dimension>& __WNElementsMultiplySSE(__WNElements<WN_FLOAT32, Dimension>& _element, const WN_FLOAT32& _number) {
            #ifdef __WN_MATH_SSE_AVAILABLE
                const WN_UINT32 nr = (Dimension + 3) >> 2;
            #else
                const WN_UINT32 ne = Dimension & 3;
                const WN_UINT32 nr = (Dimension - ne) >> 2;
            #endif

            const __m128 n = _mm_set1_ps(_number);

            for (WN_UINT32 i = 0; i < nr; ++i) {
                #ifdef __WN_MATH_SSE_AVAILABLE
                    _element.mXMMValues[i] = _mm_mul_ps(_element.mXMMValues[i], n);
                #else
                    WN_FLOAT32* a = &(_element.mValues[i << 2]);

                    _mm_store_ps(a, _mm_mul_ps(_mm_load_ps(a), n));
                #endif
            }

            #ifndef __WN_MATH_SSE_AVAILABLE
                for (WN_UINT32 i = ne; i > 0; --i) {
                    _element.mValues[Dimension - i] *= _number;
                }
            #endif

            return(_element);
        }

        template <WN_UINT32 Dimension>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, Dimension>& __WNElementsDivideSSE(__WNElements<WN_FLOAT32, Dimension>& _element1, const __WNElements<WN_FLOAT32, Dimension>& _element2) {
            #ifdef __WN_MATH_SSE_AVAILABLE
                const WN_UINT32 nr = (Dimension + 3) >> 2;
            #else
                const WN_UINT32 ne = Dimension & 3;
                const WN_UINT32 nr = (Dimension - ne) >> 2;
            #endif

            for (WN_UINT32 i = 0; i < nr; ++i) {
                #ifdef __WN_MATH_SSE_AVAILABLE
                    #ifdef __WN_MATH_ENABLE_APPROXIMATIONS
                        _element1.mXMMValues[i] = _mm_mul_ps(_element1.mXMMValues[i], _mm_rcp_ps(_element2.mXMMValues[i]));
                    #else
                        _element1.mXMMValues[i] = _mm_div_ps(_element1.mXMMValues[i], _element2.mXMMValues[i]);
                    #endif
                #else
                    WN_FLOAT32* a1 = &(_element1.mValues[i << 2]);
                    const WN_FLOAT32* a2 = &(_element2.mValues[i << 2]);

                    #ifdef __WN_MATH_ENABLE_APPROXIMATIONS
                        _mm_store_ps(a1, _mm_mul_ps(_mm_load_ps(a1), _mm_rcp_ps(_mm_load_ps(a2))));
                    #else
                        _mm_store_ps(a1, _mm_div_ps(_mm_load_ps(a1), _mm_load_ps(a2)));
                    #endif
                #endif
            }

            #ifndef __WN_MATH_SSE_AVAILABLE
                for (WN_UINT32 i = ne; i > 0; --i) {
                    _element1.mValues[Dimension - i] /= _element2.mValues[Dimension - i];
                }
            #endif

            return(_element1);
        }

        template <WN_UINT32 Dimension>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, Dimension>& __WNElementsDivideSSE(__WNElements<WN_FLOAT32, Dimension>& _element, const WN_FLOAT32& _number) {
            #ifdef __WN_MATH_SSE_AVAILABLE
                const WN_UINT32 nr = (Dimension + 3) >> 2;
            #else
                const WN_UINT32 ne = Dimension & 3;
                const WN_UINT32 nr = (Dimension - ne) >> 2;
            #endif

            const WN_FLOAT32 ni = 1.0f / _number;

            #ifdef __WN_MATH_ENABLE_APPROXIMATIONS
                const __m128 n = _mm_rcp_ps(_mm_set1_ps(_number));
            #else
                const __m128 n = _mm_set1_ps(ni);
            #endif

            for (WN_UINT32 i = 0; i < nr; ++i) {
                #ifdef __WN_MATH_SSE_AVAILABLE
                    _element.mXMMValues[i] = _mm_mul_ps(_element.mXMMValues[i], n);
                #else
                    WN_FLOAT32* a = &(_element.mValues[i << 2]);

                    _mm_store_ps(a, _mm_mul_ps(_mm_load_ps(a), n));
                #endif
            }

            #ifndef __WN_MATH_SSE_AVAILABLE
                for (WN_UINT32 i = ne; i > 0; --i) {
                    _element.mValues[Dimension - i] /= _number;
                }
            #endif

            return(_element);
        }

        template <WN_UINT32 Dimension>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, Dimension>& __WNElementsMultiplyAddSSE(__WNElements<WN_FLOAT32, Dimension>& _element1, const __WNElements<WN_FLOAT32, Dimension>& _element2, const __WNElements<WN_FLOAT32, Dimension>& _element3) {
            #ifdef __WN_MATH_SSE_AVAILABLE
                const WN_UINT32 nr = (Dimension + 3) >> 2;
            #else
                const WN_UINT32 ne = Dimension & 3;
                const WN_UINT32 nr = (Dimension - ne) >> 2;
            #endif

            for (WN_UINT32 i = 0; i < nr; ++i) {
                #ifdef __WN_MATH_SSE_AVAILABLE
                    _element1.mXMMValues[i] = _mm_add_ps(_mm_mul_ps(_element1.mXMMValues[i], _element2.mXMMValues[i]), _element3.mXMMValues[i]);
                #else
                    WN_FLOAT32* a1 = &(_element1.mValues[i << 2]);
                    const WN_FLOAT32* a2 = &(_element2.mValues[i << 2]);
                    const WN_FLOAT32* a3 = &(_element3.mValues[i << 2]);

                    _mm_store_ps(a1, _mm_add_ps(_mm_mul_ps(_mm_load_ps(a1), _mm_load_ps(a2)), _mm_load_ps(a3)));
                #endif
            }

            #ifndef __WN_MATH_SSE_AVAILABLE
                for (WN_UINT32 i = ne; i > 0; --i) {
                    _element1.mValues[Dimension - i] *= _element2.mValues[Dimension - i];
                    _element1.mValues[Dimension - i] += _element3.mValues[Dimension - i];
                }
            #endif

            return(_element1);
        }

        template <WN_UINT32 Dimension>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, Dimension>& __WNElementsMultiplyAddSSE(__WNElements<WN_FLOAT32, Dimension>& _element, const WN_FLOAT32 _number1, const WN_FLOAT32 _number2) {
            #ifdef __WN_MATH_SSE_AVAILABLE
                const WN_UINT32 nr = (Dimension + 3) >> 2;
            #else
                const WN_UINT32 ne = Dimension & 3;
                const WN_UINT32 nr = (Dimension - ne) >> 2;
            #endif

            const __m128 n1 = _mm_set1_ps(_number1);
            const __m128 n2 = _mm_set1_ps(_number2);

            for (WN_UINT32 i = 0; i < nr; ++i) {
                #ifdef __WN_MATH_SSE_AVAILABLE
                    _element.mXMMValues[i] = _mm_add_ps(_mm_mul_ps(_element.mXMMValues[i], n1), n2);
                #else
                    WN_FLOAT32* a = &(_element.mValues[i << 2]);

                    _mm_store_ps(a, _mm_add_ps(_mm_mul_ps(_mm_load_ps(a), n1), n2));
                #endif
            }

            #ifndef __WN_MATH_SSE_AVAILABLE
                for (WN_UINT32 i = ne; i > 0; --i) {
                    _element.mValues[Dimension - i] *= _number1;
                    _element.mValues[Dimension - i] += _number2;
                }
            #endif

            return(_element);
        }

        template <WN_UINT32 Dimension>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, Dimension>& __WNElementsMultiplySubtractSSE(__WNElements<WN_FLOAT32, Dimension>& _element1, const __WNElements<WN_FLOAT32, Dimension>& _element2, const __WNElements<WN_FLOAT32, Dimension>& _element3) {
            #ifdef __WN_MATH_SSE_AVAILABLE
                const WN_UINT32 nr = (Dimension + 3) >> 2;
            #else
                const WN_UINT32 ne = Dimension & 3;
                const WN_UINT32 nr = (Dimension - ne) >> 2;
            #endif

            for (WN_UINT32 i = 0; i < nr; ++i) {
                #ifdef __WN_MATH_SSE_AVAILABLE
                    _element1.mXMMValues[i] = _mm_sub_ps(_element3.mXMMValues[i], _mm_mul_ps(_element1.mXMMValues[i], _element2.mXMMValues[i]));
                #else
                    WN_FLOAT32* a1 = &(_element1.mValues[i << 2]);
                    const WN_FLOAT32* a2 = &(_element2.mValues[i << 2]);
                    const WN_FLOAT32* a3 = &(_element3.mValues[i << 2]);

                    _mm_store_ps(a1, _mm_sub_ps(_mm_load_ps(a3), _mm_mul_ps(_mm_load_ps(a1), _mm_load_ps(a2))));
                #endif
            }

            #ifndef __WN_MATH_SSE_AVAILABLE
                for (WN_UINT32 i = ne; i > 0; --i) {
                    _element1.mValues[Dimension - i] *= _element2.mValues[Dimension - i];
                    _element1.mValues[Dimension - i] = (_element3.mValues[Dimension - i] - _element1.mValues[Dimension - i]);
                }
            #endif

            return(_element1);
        }

        template <WN_UINT32 Dimension>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, Dimension>& __WNElementsMultiplySubtractSSE(__WNElements<WN_FLOAT32, Dimension>& _element, const WN_FLOAT32 _number1, const WN_FLOAT32 _number2) {
            #ifdef __WN_MATH_SSE_AVAILABLE
                const WN_UINT32 nr = (Dimension + 3) >> 2;
            #else
                const WN_UINT32 ne = Dimension & 3;
                const WN_UINT32 nr = (Dimension - ne) >> 2;
            #endif

            const __m128 n1 = _mm_set1_ps(_number1);
            const __m128 n2 = _mm_set1_ps(_number2);

            for (WN_UINT32 i = 0; i < nr; ++i) {
                #ifdef __WN_MATH_SSE_AVAILABLE
                    _element.mXMMValues[i] = _mm_sub_ps(n2, _mm_mul_ps(_element.mXMMValues[i], n1));
                #else
                    WN_FLOAT32* a = &(_element.mValues[i << 2]);

                    _mm_store_ps(a, _mm_sub_ps(n2, _mm_mul_ps(_mm_load_ps(a), n1)));
                #endif
            }

            #ifndef __WN_MATH_SSE_AVAILABLE
                for (WN_UINT32 i = ne; i > 0; --i) {
                    _element.mValues[Dimension - i] *= _number1;
                    _element.mValues[Dimension - i] = (_number2 - _element.mValues[Dimension - i]);
                }
            #endif

            return(_element);
        }

        // 4 Elements

        template <>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, 4>& __WNElementsAddSSE(__WNElements<WN_FLOAT32, 4>& _element1, const __WNElements<WN_FLOAT32, 4>& _element2) {
            #ifdef __WN_MATH_SSE_AVAILABLE
                _element1.mXMMValues[0] = _mm_add_ps(_element1.mXMMValues[0], _element2.mXMMValues[0]);
            #else
                WN_FLOAT32* a1 = &(_element1.mValues[0]);
                const WN_FLOAT32* a2 = &(_element2.mValues[0]);

                _mm_store_ps(a1, _mm_add_ps(_mm_load_ps(a1), _mm_load_ps(a2)));
            #endif

            return(_element1);
        }

        template <>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, 4>& __WNElementsAddSSE(__WNElements<WN_FLOAT32, 4>& _element, const WN_FLOAT32& _number) {
            #ifdef __WN_MATH_SSE_AVAILABLE
                _element.mXMMValues[0] = _mm_add_ps(_element.mXMMValues[0], _mm_set1_ps(_number));
            #else
                WN_FLOAT32* a = &(_element.mValues[0]);

                _mm_store_ps(a, _mm_add_ps(_mm_load_ps(a), _mm_set1_ps(_number)));
            #endif

            return(_element);
        }

        template <>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, 4>& __WNElementsSubtractSSE(__WNElements<WN_FLOAT32, 4>& _element1, const __WNElements<WN_FLOAT32, 4>& _element2) {
            #ifdef __WN_MATH_SSE_AVAILABLE
                _element1.mXMMValues[0] = _mm_sub_ps(_element1.mXMMValues[0], _element2.mXMMValues[0]);
            #else
                WN_FLOAT32* a1 = &(_element1.mValues[0]);
                const WN_FLOAT32* a2 = &(_element2.mValues[0]);

                _mm_store_ps(a1, _mm_sub_ps(_mm_load_ps(a1), _mm_load_ps(a2)));
            #endif

            return(_element1);
        }

        template <>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, 4>& __WNElementsSubtractSSE(__WNElements<WN_FLOAT32, 4>& _element, const WN_FLOAT32& _number) {
            #ifdef __WN_MATH_SSE_AVAILABLE
                _element.mXMMValues[0] = _mm_sub_ps(_element.mXMMValues[0], _mm_set1_ps(_number));
            #else
                WN_FLOAT32* a = &(_element.mValues[0]);

                _mm_store_ps(a, _mm_sub_ps(_mm_load_ps(a), _mm_set1_ps(_number)));
            #endif

            return(_element);
        }

        template <>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, 4>& __WNElementsMultiplySSE(__WNElements<WN_FLOAT32, 4>& _element1, const __WNElements<WN_FLOAT32, 4>& _element2) {
            #ifdef __WN_MATH_SSE_AVAILABLE
                _element1.mXMMValues[0] = _mm_mul_ps(_element1.mXMMValues[0], _element2.mXMMValues[0]);
            #else
                const WN_FLOAT32* a2 = &(_element2.mValues[0]);
                WN_FLOAT32* a1 = &(_element1.mValues[0]);

                _mm_store_ps(a1, _mm_mul_ps(_mm_load_ps(a1), _mm_load_ps(a2)));
            #endif

            return(_element1);
        }

        template <>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, 4>& __WNElementsMultiplySSE(__WNElements<WN_FLOAT32, 4>& _element, const WN_FLOAT32& _number) {
            #ifdef __WN_MATH_SSE_AVAILABLE
                _element.mXMMValues[0] = _mm_mul_ps(_element.mXMMValues[0], _mm_set1_ps(_number));
            #else
                WN_FLOAT32* a = &(_element.mValues[0]);

                _mm_store_ps(a, _mm_mul_ps(_mm_load_ps(a), _mm_set1_ps(_number)));
            #endif

            return(_element);
        }

        template <>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, 4>& __WNElementsDivideSSE(__WNElements<WN_FLOAT32, 4>& _element1, const __WNElements<WN_FLOAT32, 4>& _element2) {
            #ifdef __WN_MATH_SSE_AVAILABLE
                #ifdef __WN_MATH_ENABLE_APPROXIMATIONS
                    _element1.mXMMValues[0] = _mm_mul_ps(_element1.mXMMValues[0], _mm_rcp_ps(_element2.mXMMValues[0]));
                #else
                    _element1.mXMMValues[0] = _mm_div_ps(_element1.mXMMValues[0], _element2.mXMMValues[0]);
                #endif
            #else
                const WN_FLOAT32* a2 = &(_element2.mValues[0]);
                WN_FLOAT32* a1 = &(_element1.mValues[0]);

                #ifdef __WN_MATH_ENABLE_APPROXIMATIONS
                    _mm_store_ps(a1, _mm_mul_ps(_mm_load_ps(a1), _mm_rcp_ps(_mm_load_ps(a2))));
                #else
                    _mm_store_ps(a1, _mm_div_ps(_mm_load_ps(a1), _mm_load_ps(a2)));
                #endif
            #endif

            return(_element1);
        }

        template <>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, 4>& __WNElementsDivideSSE(__WNElements<WN_FLOAT32, 4>& _element, const WN_FLOAT32& _number) {
            #ifdef __WN_MATH_SSE_AVAILABLE
                #ifdef __WN_MATH_ENABLE_APPROXIMATIONS
                    _element.mXMMValues[0] = _mm_mul_ps(_element.mXMMValues[0], _mm_rcp_ps(_mm_set1_ps(_number)));
                #else
                    _element.mXMMValues[0] = _mm_mul_ps(_element.mXMMValues[0], _mm_set1_ps(1.0f / _number));
                #endif
            #else
                WN_FLOAT32* a = &(_element.mValues[0]);

                #ifdef __WN_MATH_ENABLE_APPROXIMATIONS
                    _mm_store_ps(a, _mm_mul_ps(_mm_load_ps(a), _mm_rcp_ps(_mm_set1_ps(_number))));
                #else
                    _mm_store_ps(a, _mm_mul_ps(_mm_load_ps(a), _mm_set1_ps(1.0f / _number)));
                #endif
            #endif

            return(_element);
        }

        // 3 Elements

        template <>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, 3>& __WNElementsMultiplySSE(__WNElements<WN_FLOAT32, 3>& _element1, const __WNElements<WN_FLOAT32, 3>& _element2) {
            #ifdef __WN_MATH_SSE_AVAILABLE
                _element1.mXMMValues[0] = _mm_mul_ps(_element1.mXMMValues[0], _element2.mXMMValues[0]);
            #else
                WN_FLOAT32* a1a = &(_element1.mValues[0]);
                WN_FLOAT32* a1b = &(_element1.mValues[1]);
                WN_FLOAT32* a1c = &(_element1.mValues[2]);
                const WN_FLOAT32* a2a = &(_element2.mValues[0]);
                const WN_FLOAT32* a2b = &(_element2.mValues[1]);
                const WN_FLOAT32* a2c = &(_element2.mValues[2]);

                const __m128 v1 = _mm_movelh_ps(_mm_loadl_pi(_mm_setzero_ps(), reinterpret_cast<const __m64*>(a1a)), _mm_load_ss(a1c));
                const __m128 v2 = _mm_movelh_ps(_mm_loadl_pi(_mm_setzero_ps(), reinterpret_cast<const __m64*>(a2a)), _mm_load_ss(a2c));
                const __m128 r = _mm_mul_ps(v1, v2);

                _mm_store_ss(a1a, r);
                _mm_store_ss(a1b, _mm_shuffle_ps(r, r, _MM_SHUFFLE(3, 3, 3, 1)));
                _mm_store_ss(a1c, _mm_shuffle_ps(r, r, _MM_SHUFFLE(3, 3, 3, 2)));
            #endif

            return(_element1);
        }

        template <>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, 3>& __WNElementsMultiplySSE(__WNElements<WN_FLOAT32, 3>& _element, const WN_FLOAT32& _number) {
            #ifdef __WN_MATH_SSE_AVAILABLE
                _element.mXMMValues[0] = _mm_mul_ps(_element.mXMMValues[0], _mm_set1_ps(_number));
            #else
                WN_FLOAT32* a1 = &(_element.mValues[0]);
                WN_FLOAT32* a2 = &(_element.mValues[1]);
                WN_FLOAT32* a3 = &(_element.mValues[2]);

                const __m128 v = _mm_movelh_ps(_mm_loadl_pi(_mm_setzero_ps(), reinterpret_cast<const __m64*>(a1)), _mm_load_ss(a3));
                const __m128 r = _mm_mul_ps(v, _mm_set1_ps(_number));

                _mm_store_ss(a1, r);
                _mm_store_ss(a2, _mm_shuffle_ps(r, r, _MM_SHUFFLE(3, 3, 3, 1)));
                _mm_store_ss(a3, _mm_shuffle_ps(r, r, _MM_SHUFFLE(3, 3, 3, 2)));
            #endif

            return(_element);
        }

        template <>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, 3>& __WNElementsDivideSSE(__WNElements<WN_FLOAT32, 3>& _element1, const __WNElements<WN_FLOAT32, 3>& _element2) {
            #ifdef __WN_MATH_SSE_AVAILABLE
                #ifdef __WN_MATH_ENABLE_APPROXIMATIONS
                    _element1.mXMMValues[0] = _mm_mul_ps(_element1.mXMMValues[0], _mm_rcp_ps(_element2.mXMMValues[0]));
                #else
                    _element1.mXMMValues[0] = _mm_div_ps(_element1.mXMMValues[0], _element2.mXMMValues[0]);
                #endif
            #else
                WN_FLOAT32* a1a = &(_element1.mValues[0]);
                WN_FLOAT32* a1b = &(_element1.mValues[1]);
                WN_FLOAT32* a1c = &(_element1.mValues[2]);
                const WN_FLOAT32* a2a = &(_element2.mValues[0]);
                const WN_FLOAT32* a2b = &(_element2.mValues[1]);
                const WN_FLOAT32* a2c = &(_element2.mValues[2]);

                const __m128 v1 = _mm_movelh_ps(_mm_loadl_pi(_mm_setzero_ps(), reinterpret_cast<const __m64*>(a1a)), _mm_load_ss(a1b));
                const __m128 v2 = _mm_movelh_ps(_mm_loadl_pi(_mm_setzero_ps(), reinterpret_cast<const __m64*>(a2a)), _mm_load_ss(a2b));

                #ifdef __WN_MATH_ENABLE_APPROXIMATIONS
                    const __m128 r = _mm_mul_ps(v1, _mm_rcp_ps(v2));
                #else
                    const __m128 r = _mm_div_ps(v1, v2);
                #endif

                _mm_store_ss(a1a, r);
                _mm_store_ss(a1b, _mm_shuffle_ps(r, r, _MM_SHUFFLE(3, 3, 3, 1)));
                _mm_store_ss(a1c, _mm_shuffle_ps(r, r, _MM_SHUFFLE(3, 3, 3, 2)));
            #endif

            return(_element1);
        }

        template <>
        __WN_MATH_SSE_INLINE __WNElements<WN_FLOAT32, 3>& __WNElementsDivideSSE(__WNElements<WN_FLOAT32, 3>& _element, const WN_FLOAT32& _number) {
            #ifdef __WN_MATH_SSE_AVAILABLE
                #ifdef __WN_MATH_ENABLE_APPROXIMATIONS
                    _element.mXMMValues[0] = _mm_mul_ps(_element.mXMMValues[0], _mm_rcp_ps(_mm_set1_ps(_number)));
                #else
                    _element.mXMMValues[0] = _mm_mul_ps(_element.mXMMValues[0], _mm_set1_ps(1.0f / _number));
                #endif
            #else
                WN_FLOAT32* a1 = &(_element.mValues[0]);
                WN_FLOAT32* a2 = &(_element.mValues[1]);
                WN_FLOAT32* a3 = &(_element.mValues[2]);

                const __m128 v = _mm_movelh_ps(_mm_loadl_pi(_mm_setzero_ps(), reinterpret_cast<const __m64*>(a1)), _mm_load_ss(a3));

                #ifdef __WN_MATH_ENABLE_APPROXIMATIONS
                    const __m128 r = _mm_mul_ps(v, _mm_rcp_ps(_mm_set1_ps(_number)));
                #else
                    const __m128 r = _mm_mul_ps(v, _mm_set1_ps(1.0f / _number));
                #endif

                _mm_store_ss(a1, r);
                _mm_store_ss(a2, _mm_shuffle_ps(r, r, _MM_SHUFFLE(3, 3, 3, 1)));
                _mm_store_ss(a3, _mm_shuffle_ps(r, r, _MM_SHUFFLE(3, 3, 3, 2)));
            #endif

            return(_element);
        }
    }
}

#endif // __WN_MATH_INTERNAL_X86_SSE_ELEMENTS_INL__